package java_api

import (
	"bsfswagger/base"
	"bytes"
	"fmt"
	"html/template"
	"os"
	"strings"
)

type JavaTemplate struct {
	Pkg              string
	OutDir           string
	Api              *base.Api
	ReplaceTypeNames []string
}

func (m *JavaTemplate) Render() {
	for _, replace := range strings.Split("_,-", ",") {
		m.Pkg = strings.Replace(m.Pkg, replace, ".", -1)
	}
	buf := bytes.Buffer{}
	tmpl := base.TemplateUtil.TryGetTemplateFromCache("api.tmpl", []string{"java_api/api.tmpl"})
	base.TemplateUtil.RenderTemplate(&buf, "api.tmpl", tmpl, map[string]any{
		"api": *m.Api,
		"m":   m,
		"pkg": m.Pkg,
	})
	dir := m.OutDir + "/" + m.GetStructName()
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile(dir+"/"+m.ClientName()+".java", buf.Bytes(), 0644)
	if err != nil {
		panic(err)
	}
}

func (m *JavaTemplate) GetStructName() string {
	dirs := strings.Split(m.Pkg, ".")
	return dirs[len(dirs)-1]
}

func (m *JavaTemplate) ClientName() string {
	return m.upperFirstLetter(base.CommonUtil.UnderScoreToCamelCase(m.GetStructName())) + "Client"
}
func (m *JavaTemplate) ModelName(modelName string) string {
	r := modelName
	r = base.CommonUtil.ReplaceStrs(r, strings.Split("-,.", ","), "_")
	r = base.CommonUtil.ReplaceStr(r, "__", "_")
	r = base.CommonUtil.TrimStr(r, "_")
	for _, replace := range m.ReplaceTypeNames {
		r = base.CommonUtil.ReplaceStr(r, replace, "")
	}
	return m.upperFirstLetter(base.CommonUtil.UnderScoreToCamelCase(r))
}

func (m *JavaTemplate) ModelRemark(model base.Model) string {
	var r strings.Builder
	r.WriteString(fmt.Sprintf("\t//%v\n", m.ModelName(model.Name)))
	if model.Name != "" {
		r.WriteString(fmt.Sprintf("\t//@Name %v\n", model.Name))
	}
	if model.Description != "" {
		r.WriteString(fmt.Sprintf("\t//@Description %v\n", model.Description))
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *JavaTemplate) PropertiesStr(model base.Model) string {
	var r strings.Builder
	for _, p := range model.Properties {
		r.WriteString(fmt.Sprintf("\t\t%v %v; //%v example(%v)\n", m.getPropertyType(p), m.upperFirstLetter(p.Name), p.Description, p.Example))
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *JavaTemplate) getPropertyType(property base.Property) string {
	return m.toType(property.Type, property.IsArray)
}

func (m *JavaTemplate) MethodName(methodName string) string {
	r := methodName
	r = base.CommonUtil.ReplaceStrs(r, strings.Split("{,},/,?,&,-,.", ","), "_")
	r = base.CommonUtil.ReplaceStr(r, "__", "_")
	r = base.CommonUtil.TrimStr(r, "_")
	return m.lowerFirstLetter(base.CommonUtil.UnderScoreToCamelCase(r))
}

func (m *JavaTemplate) MethodRemark(method base.Method) string {
	var r strings.Builder
	r.WriteString(fmt.Sprintf("\t//%v\n", m.MethodName(method.Path)))
	r.WriteString(fmt.Sprintf("\t//@UrlPath %v [%v]\n", method.Path, method.HttpMethod))
	if method.Summary != "" {
		r.WriteString(fmt.Sprintf("\t//@Summary %v\n", method.Summary))
	}
	if method.Description != "" {
		r.WriteString(fmt.Sprintf("\t//@Description %v\n", method.Description))
	}
	for _, p := range method.Parameters {
		r.WriteString(fmt.Sprintf("\t//@Param %v  %v default(%v) required(%v) //%v\n", p.Name, m.getParameterType(p), p.Default, p.Required, p.Description))
	}
	for _, p := range method.Returns {
		if p.Code == "200" {
			r.WriteString(fmt.Sprintf("\t//@return %v %v //%v\n", p.Name, m.getReturnType(p), p.Description))
		}
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *JavaTemplate) Request(method base.Method) string {
	var r0 strings.Builder
	r0.WriteString("\t\tval apiRequest=new HashMap<String, Object>();\n")
	r0.WriteString(fmt.Sprintf("\t\tapiRequest.put(\"Title\",%#v);", m.Api.Title))
	r0.WriteString(fmt.Sprintf("apiRequest.put(\"BasePath\",%#v);", m.Api.BasePath))
	r0.WriteString(fmt.Sprintf("apiRequest.put(\"HttpMethod\",%#v);", method.HttpMethod))
	r0.WriteString(fmt.Sprintf("apiRequest.put(\"Path\",%#v);", method.Path))
	if len(method.Consumes) > 0 {
		r0.WriteString(strings.Replace(fmt.Sprintf("apiRequest.put(\"Consumes\",%#v);", method.Consumes), "[]string{", "new String[]{", -1))
	}
	if len(method.Produces) > 0 {
		r0.WriteString(strings.Replace(fmt.Sprintf("apiRequest.put(\"Produces\",%#v);", method.Produces), "[]string{", "new String[]{", -1))
	}
	paramTypes := []string{"path", "query", "body", "formData"}
	for _, paramType := range paramTypes {
		if len(method.Parameters) > 0 {
			r0.WriteString(fmt.Sprintf("\n\t\tval %vParams=new HashMap<String, Object>();", paramType))
			for _, p := range method.Parameters {
				if strings.ToLower(p.In) == strings.ToLower(paramType) {
					r0.WriteString(fmt.Sprintf("%vParams.put(\"%v\",%v);", paramType, p.Name, p.Name))
				}
			}
			r0.WriteString(fmt.Sprintf("apiRequest.put(\"%vParams\",%vParams);", m.upperFirstLetter(paramType), paramType))
		}
	}
	returnStr := m.ReturnStr(method)
	r0.WriteString(fmt.Sprintf("\n\t\treturn this.ApiClient.executeWithRetry(apiRequest,new TypeReference<%v>(){});\n", returnStr))
	return r0.String()
}

func (m *JavaTemplate) ParamsStr(method base.Method) string {
	var r strings.Builder
	for _, p := range method.Parameters {
		r.WriteString(fmt.Sprintf("%v %v,", m.getParameterType(p), p.Name))
	}
	return strings.TrimRight(r.String(), ",")
}

func (m *JavaTemplate) getParameterType(parameter base.Parameter) string {
	return m.toType(parameter.Type, parameter.IsArray)
}

func (m *JavaTemplate) ReturnStr(method base.Method) string {
	for _, p := range method.Returns {
		if p.Code == "200" {
			return fmt.Sprintf("%v", m.getReturnType(p))
		}
	}
	return ""
}

func (m *JavaTemplate) getReturnType(parameter base.ReturnParameter) string {
	return m.toType(parameter.Type, parameter.IsArray)
}

func (m *JavaTemplate) upperFirstLetter(str string) string {
	if len(str) == 0 {
		return str
	}
	return strings.ToUpper(string(str[0])) + str[1:]
}

func (m *JavaTemplate) lowerFirstLetter(str string) string {
	if len(str) == 0 {
		return str
	}
	return strings.ToLower(string(str[0])) + str[1:]
}

func (m *JavaTemplate) toType(typeName string, isArray bool) string {
	typeNameStr := ""
	switch typeName {
	case "string":
		typeNameStr = "String"
	case "integer":
		typeNameStr = "Long"
	case "number":
		typeNameStr = "Float"
	case "boolean":
		typeNameStr = "Boolean"
	case "":
		typeNameStr = "Object"
	case "object":
		typeNameStr = "Object"
	default:
		typeNameStr = m.ModelName(typeName)
	}
	if isArray {
		return typeNameStr + "[]"
	} else {
		return typeNameStr
	}
}

func (m *JavaTemplate) Html(html string) template.HTML {
	return template.HTML(html)
}
